package execmd

import (
	"context"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

type Exectab struct {
	Name        string            `json:"label" yaml:"label,omitempty"`
	Command     string            `json:"command" yaml:"command"`
	Args        []string          `json:"args" yaml:"args,omitempty"`
	Path        string            `json:"path" yaml:"path,omitempty"`
	Dir         string            `json:"dir" yaml:"dir,omitempty"`
	Environment map[string]string `json:"environment" yaml:"environment,omitempty"`
	LogFile     string            `json:"logFile" yaml:"logFile,omitempty"`
	RawOutput   bool              `json:"rawOut" yaml:"rawOut"`
	Input       string            `json:"input" yaml:"input,omitempty"`
	StopTimeout time.Duration     `json:"stoptimeout" yaml:"stoptimeout"`

	//cannel     func()
	log        *log.Logger
	loggerInit sync.Once
}

type Runtab struct {
	Exec   *Exectab `json:"service"`
	Runnig bool     `json:"running"`
	cannel func()
}

// var cmdchannel chan *Exectab
func Cmdpool(Ctx context.Context, runtabs map[string]*Runtab, exec <-chan *Exectab) {
	lock := sync.RWMutex{}
	for {
		select {
		case <-Ctx.Done():
			return
		case exectab := <-exec:
			// 判断是否在runtabs中
			lock.Lock()
			if v, ok := runtabs[exectab.Name]; ok {
				if !v.Runnig {
					ctx, cannel := context.WithCancel(Ctx)
					v.cannel = cannel
					go v.run(ctx)
				}

			} else {
				ctx, cannel := context.WithCancel(Ctx)
				runtab := &Runtab{Exec: exectab, cannel: cannel}
				runtabs[exectab.Name] = runtab
				go runtab.run(ctx)
			}
			lock.Unlock()
		}
	}
}

func (run *Runtab) run(ctx context.Context) error {
	if !run.Runnig {
		run.Runnig = true
	}
	defer func() { run.Runnig = false }()
	return run.Exec.run(ctx)
}

func (run *Runtab) Stop() {
	run.cannel()
}

// 设置日志及日志前缀
func (exe *Exectab) logger() *log.Logger {
	exe.loggerInit.Do(func() {
		exe.log = log.New(os.Stderr, "["+exe.Name+"] ", log.LstdFlags)
	})
	return exe.log
}

func (exe *Exectab) run(ctx context.Context) error {
	cmd := exec.Command(exe.Command, exe.Args...)
	if exe.Input != "" && exe.Command == "bash" {
		cmd.Stdin.Read([]byte(exe.Input))
	}
	// 设置系统环境变量
	cmd.Env = append(cmd.Env, os.Environ()...)
	if exe.Environment != nil {
		for k, v := range exe.Environment {
			cmd.Env = append(cmd.Env, k+"="+v)
		}
	}

	if exe.Dir != "" {
		cmd.Dir = exe.Dir
	}
	// 设置系统平台参数
	setAttrs(cmd)

	var outputs []io.Writer
	var stderr []io.Writer
	var stdout []io.Writer
	// 设置命令日志前缀
	output := NewLoggerStream(exe.logger(), "out:")
	outputs = append(outputs, output)
	defer output.Close()
	res := make(chan error, 1)
	// 设置输出日志文件
	if exe.LogFile != "" {
		pth, _ := filepath.Abs(exe.LogFile)
		if pth != exe.LogFile {
			// relative
			wd, _ := filepath.Abs(exe.Dir)
			exe.LogFile = filepath.Join(wd, exe.LogFile)
		}
		logFile, err := os.OpenFile(exe.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
		if err != nil {
			exe.logger().Println("Failed open log file:", err)
		} else {
			defer logFile.Close()
			outputs = append(outputs, logFile)
		}
	}
	stderr = outputs
	stdout = outputs
	if exe.RawOutput {
		stdout = append(stdout, os.Stdout)
	}
	// 将命令的标准输出与错误输出复制写出
	logStderrStream := io.MultiWriter(stderr...)
	logStdoutStream := io.MultiWriter(stdout...)

	cmd.Stderr = logStderrStream
	cmd.Stdout = logStdoutStream
	// 开启命令
	err := cmd.Start()
	if err == nil {
		exe.logger().Println("Started with PID", cmd.Process.Pid)
	} else {
		exe.logger().Println("Failed start [", exe.Command, strings.Join(exe.Args, " "), "]:", err)
	}

	go func() { res <- cmd.Wait() }()
	select {
	case <-ctx.Done():
		err = exe.stopOrKill(cmd, res)
	case err = <-res:
	}
	return err
}

func (exe *Exectab) stopOrKill(cmd *exec.Cmd, res <-chan error) error {
	exe.logger().Println("Sending SIGINT")
	err := cmd.Process.Signal(os.Interrupt)
	if err != nil {
		exe.logger().Println("Failed send SIGINT:", err)
	}

	select {
	case err = <-res:
		exe.logger().Println("Process gracefull stopped")
	case <-time.After(exe.StopTimeout):
		exe.logger().Println("Process gracefull shutdown waiting timeout")
		err = kill(cmd, exe.logger())
	}
	return err
}
